{
 "cells": [
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "from IPython.core.interactiveshell import InteractiveShell\n",
    "InteractiveShell.ast_node_interactivity = 'all'"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### 1.构建一个模块的层级包\n",
    "\n",
    "封装成包是很简单的。在文件系统上组织你的代码，并确保每个目录都定义了一个__init__.py文件。"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### 2.控制模块被全部导入的内容\n",
    "\n",
    "当使用`from module import *` 语句时，希望对从模块或包导出的符号进行精确控制。\n",
    "\n",
    "在你的模块中定义一个变量 __all__ 来明确地列出需要导出的内容。\n",
    "\n",
    "```python\n",
    "\n",
    "# somemodule.py\n",
    "def spam():\n",
    "    pass\n",
    "\n",
    "def grok():\n",
    "    pass\n",
    "\n",
    "blah = 42\n",
    "# Only export 'spam' and 'grok'\n",
    "__all__ = ['spam', 'grok']\n",
    "```\n",
    "\n",
    "导入注意事项：\n",
    "- `import *`将会导入所有不以下划线开头的\n",
    "- 如果定义了 __all__ , 那么只有被列举出的东西会被导出\n",
    "- 如果你将 __all__ 定义成一个空列表, 没有东西将被导入\n",
    "- 如果 __all__ 包含未定义的名字, 在导入时引起AttributeError"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### 3.使用相对路径名导入包中子模块"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "```\n",
    "mypackage/\n",
    "    __init__.py\n",
    "    A/\n",
    "        __init__.py\n",
    "        spam.py\n",
    "        grok.py\n",
    "    B/\n",
    "        __init__.py\n",
    "        bar.py\n",
    "```"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "如果模块mypackage.A.spam要导入同目录下的模块grok，它应该包括的import语句如下：\n",
    "\n",
    "```\n",
    "# mypackage/A/spam.py\n",
    "from . import grok\n",
    "```\n",
    "\n",
    "如果模块mypackage.A.spam要导入不同目录下的模块B.bar，它应该使用的import语句如下：\n",
    "\n",
    "```\n",
    "# mypackage/A/spam.py\n",
    "from ..B import bar\n",
    "```"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "在包内，既可以使用相对路径也可以使用绝对路径来导入。\n",
    "\n",
    "像 mypackage.A 这样使用绝对路径名的不利之处是这将顶层包名硬编码到你的源码中。如果你想重新组织它，你的代码将更脆，很难工作。\n",
    "\n",
    "import语句的 . 和 .. 看起来很滑稽, 但它指定目录名.为当前目录，..B为目录../B。这种语法只适用于 from-import。\n",
    "\n",
    "```python\n",
    "from . import grok # OK\n",
    "import .grok # ERROR\n",
    "```\n",
    "\n",
    "相对导入只适用于在合适的包中的模块，在顶层的脚本的简单模块中，它们将不起作用。\n",
    "\n",
    "如果包的部分被作为脚本直接执行，那它们将不起作用。\n",
    "\n",
    "如果你使用Python的-m选项来执行先前的脚本，相对导入将会正确运行。\n",
    "\n",
    "```python\n",
    "python3 mypackage/A/spam.py # Relative imports fail\n",
    "\n",
    "python3 -m mypackage.A.spam # Relative imports work\n",
    "```"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### 4.将模块分割成多个文件\n"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "单个模块(单文件)\n",
    "```python\n",
    "# mymodule.py\n",
    "class A:\n",
    "    def spam(self):\n",
    "        print('A.spam')\n",
    "\n",
    "class B(A):\n",
    "    def bar(self):\n",
    "        print('B.bar')\n",
    "\n",
    "```\n"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "单个包\n",
    "\n",
    "```\n",
    "mymodule/\n",
    "    __init__.py\n",
    "    a.py\n",
    "    b.py\n",
    "```\n",
    "\n",
    "\n",
    "```python\n",
    "# a.py\n",
    "class A:\n",
    "    def spam(self):\n",
    "        print('A.spam')\n",
    "```\n",
    "\n",
    "```python\n",
    "# b.py\n",
    "from .a import A\n",
    "class B(A):\n",
    "    def bar(self):\n",
    "        print('B.bar')\n",
    "```\n",
    "\n",
    "```python\n",
    "# __init__.py\n",
    "from .a import A\n",
    "from .b import B\n",
    "\n",
    "```\n",
    "\n",
    "\n",
    "按照这些步骤，所产生的包 mymodule 将作为一个单一的逻辑模块:\n",
    "\n",
    "```python\n",
    ">>> import mymodule\n",
    ">>> a = mymodule.A()\n",
    ">>> a.spam()\n",
    "A.spam\n",
    ">>> b = mymodule.B()\n",
    ">>> b.bar()\n",
    "B.bar\n",
    ">>>\n",
    "```"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "### 5.利用命名空间导入目录分散的代码\n"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "```\n",
    "foo-package/\n",
    "    spam/\n",
    "        blah.py\n",
    "\n",
    "bar-package/\n",
    "    spam/\n",
    "        grok.py\n",
    "```\n",
    "\n",
    "两个不同的包目录被合并到一起，你可以导入spam.blah和spam.grok，并且它们能够工作。\n",
    "\n",
    "```python\n",
    ">>> import sys\n",
    ">>> sys.path.extend(['foo-package', 'bar-package'])\n",
    ">>> import spam.blah\n",
    ">>> import spam.grok\n",
    ">>>\n",
    "```"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### 6.重新加载模块\n",
    "\n",
    "使用imp.reload()来重新加载先前加载的模块。\n",
    "\n",
    "在生产环境中可能需要避免重新加载模块。\n",
    "\n",
    "```python\n",
    ">>> import spam\n",
    ">>> import imp\n",
    ">>> imp.reload(spam)\n",
    "<module 'spam' from './spam.py'>\n",
    ">>>\n",
    "```"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### 7.运行目录或压缩文件\n",
    "\n",
    "如果你的应用程序已经有多个文件，你可以把你的应用程序放进它自己的目录并添加一个__main__.py文件。\n",
    "\n",
    "\n",
    "```\n",
    "myapplication/\n",
    "    spam.py\n",
    "    bar.py\n",
    "    grok.py\n",
    "    __main__.py\n",
    "```\n",
    "\n",
    "如果__main__.py存在，你可以简单地在顶级目录运行Python解释器，解释器将执行__main__.py文件作为主程序。\n",
    "\n",
    "`bash % python3 myapplication`"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### 8.读取位于包中的数据文件\n",
    "\n",
    "假设你的包中的文件组织成如下：\n",
    "\n",
    "```\n",
    "mypackage/\n",
    "    __init__.py\n",
    "    somedata.dat\n",
    "    spam.py\n",
    "```\n",
    "\n",
    "现在假设spam.py文件需要读取somedata.dat文件中的内容。你可以用以下代码来完成：\n",
    "\n",
    "```python\n",
    "# spam.py\n",
    "import pkgutil\n",
    "data = pkgutil.get_data(__package__, 'somedata.dat')\n",
    "```\n",
    "\n",
    "data是包含该文件的原始内容的字节字符串。"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### 9.将文件夹加入到sys.path\n",
    "\n",
    "有两种常用的方式将新目录添加到sys.path。\n",
    "\n",
    "第一种，你可以使用PYTHONPATH环境变量来添加。\n",
    "\n",
    "```\n",
    "bash % env PYTHONPATH=/some/dir:/other/dir python3\n",
    "Python 3.3.0 (default, Oct 4 2012, 10:17:33)\n",
    "[GCC 4.2.1 (Apple Inc. build 5666) (dot 3)] on darwin\n",
    "Type \"help\", \"copyright\", \"credits\" or \"license\" for more information.\n",
    ">>> import sys\n",
    ">>> sys.path\n",
    "['', '/some/dir', '/other/dir', ...]\n",
    ">>>\n",
    "\n",
    "```\n",
    "\n",
    "第二种方法是创建一个.pth文件，将目录列举出来。\n",
    "\n",
    "这个.pth文件需要放在某个Python的site-packages目录，当解释器启动时，.pth文件里列举出来的存在于文件系统的目录将被添加到sys.path。\n",
    "\n",
    "```\n",
    "# myapplication.pth\n",
    "/some/dir\n",
    "/other/dir\n",
    "\n",
    "```"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### 10.通过字符串名导入模块\n",
    "\n",
    "使用importlib.import_module()函数来手动导入名字为字符串给出的一个模块或者包的一部分。\n",
    "\n",
    "import_module只是简单地执行和import相同的步骤，但是返回生成的模块对象。你只需要将其存储在一个变量，然后像正常的模块一样使用。\n",
    "\n",
    "```\n",
    ">>> import importlib\n",
    ">>> math = importlib.import_module('math')\n",
    ">>> math.sin(2)\n",
    "0.9092974268256817\n",
    ">>> mod = importlib.import_module('urllib.request')\n",
    ">>> u = mod.urlopen('http://www.python.org')\n",
    ">>>\n",
    "\n",
    "```"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### 11.通过钩子远程加载模块\n",
    "\n",
    "你想自定义Python的import语句，使得它能从远程机器上面透明的加载模块。\n",
    "\n",
    "肯定用不到这功能，用不到。\n",
    "\n",
    "所以，本小节内容不读了。\n",
    "\n",
    "太复杂了，很难懂这部分内容。"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### 12.导入模块的同时修改模块\n",
    "\n",
    "你想给某个已存在模块中的函数添加装饰器。 不过，前提是这个模块已经被导入并且被使用过。\n",
    "\n",
    "这一小节也跳过，因为它依赖第11小节内容。"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### 13.安装私有的包\n",
    "\n",
    "你想要安装一个第三方包，但是没有权限将它安装到系统Python库中去。 或者，你可能想要安装一个供自己使用的包，而不是系统上面所有用户。\n",
    "\n",
    "Python有一个用户安装目录，通常类似”~/.local/lib/python3.3/site-packages”。 要强制在这个目录中安装包，可使用安装选项“–user”。\n",
    "\n",
    "```\n",
    "python3 setup.py install --user\n",
    "\n",
    "或者\n",
    "\n",
    "pip install --user packagename\n",
    "\n",
    "```"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### 14.创建新的Python环境\n",
    "\n",
    "你想创建一个新的Python环境，用来安装模块和包。 不过，你不想安装一个新的Python克隆，也不想对系统Python环境产生影响。\n",
    "\n",
    "你可以使用 pyvenv 命令创建一个新的“虚拟”环境。\n",
    "\n",
    "> 使用 pyvenv 命令的方式已经过时了，现在python3已经支持 venv 模块了\n",
    "\n",
    "使用方式如下：\n",
    "\n",
    "```bash\n",
    "# 创建虚拟python环境\n",
    "# 这会在当前目录下创建一个`.venv`的目录，这个`.venv`目录就是虚拟环境下各种包代码的存储目录\n",
    "# 进入虚拟环境下的各种安装包的操作，都会安装到这个`.venv`目录\n",
    "python3 -m venv .venv\n",
    "\n",
    "# windows下进入虚拟环境\n",
    ".venv\\Scripts\\activate.bat\n",
    ".\\.venv\\Scripts\\Activate.ps1\n",
    "\n",
    "# linux下进入虚拟环境\n",
    "source ./.venv/bin/activate\n",
    "\n",
    "```\n"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### 15.分发包\n",
    "\n",
    "你已经编写了一个有用的库，想将它分享给其他人。\n",
    "\n",
    "不读了，应该用不到这个。\n",
    "\n",
    "https://python3-cookbook.readthedocs.io/zh_CN/latest/c10/p15_distributing_packages.html"
   ]
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "Python 3.9.7 ('base')",
   "language": "python",
   "name": "python3"
  },
  "language_info": {
   "name": "python",
   "version": "3.9.7"
  },
  "orig_nbformat": 4,
  "vscode": {
   "interpreter": {
    "hash": "caf1c2fcf97217de91eafa76b907d50f9ea378f5ffbee7f571142d119bb6a771"
   }
  }
 },
 "nbformat": 4,
 "nbformat_minor": 2
}
